<?php
/**
 * | ---------------------------------------------------------------------------------------------------
 * | Author：johnxu <fsyzxz@163.com>.
 * | ---------------------------------------------------------------------------------------------------
 * | Home: https://www.johnxu.net.
 * | ---------------------------------------------------------------------------------------------------
 * | Data: 2019-03-18
 * | ---------------------------------------------------------------------------------------------------
 * | Desc:
 * | ---------------------------------------------------------------------------------------------------
 */

namespace app\service\websocket;

use app\service\websocket\handler\Driver;
use think\Db;
use think\Exception;
use think\Loader;

class Handler
{
    /**
     * @var Handler
     */
    static private $instance = null;

    private $drivers = [];

    /**
     * @var \swoole_websocket_server
     */
    private $server;

    /**
     * @var Handler
     */
    protected $handler;

    private function __construct( \swoole_websocket_server $server )
    {
        $this->server = $server;
    }

    /**
     * @param \swoole_websocket_server $server
     *
     * @return Handler
     */
    public static function getInstance( \swoole_websocket_server $server )
    {
        if ( is_null( self::$instance ) )
        {
            self::$instance = new self( $server );
        }

        return self::$instance;
    }

    /**
     * 消息转发
     *
     * @param \swoole_websocket_frame $frame
     */
    public function dispatch( \swoole_websocket_frame $frame )
    {
        try
        {
            if ( $frame->data == 'PING' )
            {
                $instance = $this->connect( [ 'type' => 'ping', 'content' => 'PING' ] );
            }
            else
            {
                $data = $this->getData( $frame->data );
                // 根据消息类型进行分发
                $instance = $this->connect( $data );
            }

            $instance->handler( $this->server, $frame->fd );
        }
        catch ( \think\exception\ClassNotFoundException $e )
        {
            $this->server->exist( $frame->fd ) && $this->server->push( $frame->fd, '{"type": "error", "message": "消息类型错误"}' );
        }
    }

    /**
     * 处理掉线的问题
     *
     * @param $fd
     *
     * @return $this
     */
    public function close( $fd )
    {
        $user       = Db::name( 'users' )->where( [ 'fd' => $fd ] )->find();
        $updateData = [
            'fd'    => 0,
            'state' => 'offline'
        ];
        if ( $user )
        {
            Db::name( 'users' )->where( [ 'id' => $user['id'] ] )->update( $updateData );
        }
        if ( $user && $user['type'] == 'service' )
        {
            // notice to all users say: service offline
            print("客服{$user['nickname']}下线了");
        }
        else
        {
            //todo: push to service say: user offline
            print('游客' . $fd . '吊线了');
        }
    }

    /**
     * @param array $options
     * @param bool  $name
     *
     * @return Driver
     */
    private function connect( $options = [], $name = false )
    {
        if ( false === $name )
        {
            $name = md5( serialize( $options ) );
        }

        if ( true === $name || !isset( $this->instance[$name] ) )
        {
            $type = (!isset( $options['type'] ) || !empty( $options['type'] )) ? strtolower( $options['type'] ) : 'Error';

            if ( true === $name )
            {
                $name = md5( serialize( $options ) );
            }

            $this->drivers[$name] = Loader::factory( $type, 'app\\service\\websocket\\handler\\drivers\\', $options );
        }

        return $this->drivers[$name];
    }

    /**
     * 获取用户发送的数据
     *
     * @param string $data
     *
     * @return array|mixed|string
     */
    private function getData( string $data )
    {
        $data = json_decode( $data, true );
        if ( JSON_ERROR_NONE !== json_last_error() )
        {
            $data = [
                'type'    => 'error',
                'message' => '传输数据有误'
            ];
        }

        if ( !isset( $data['type'] ) || empty( $data['type'] ) )
        {
            $data = [
                'type'    => 'error',
                'message' => '数据类型错误'
            ];
        }

        return $data;
    }
}